Debugging and Testing a Macro Model
Macro offers a range of utility functions designed to make debugging and testing new models and sectors more efficient and straightforward.
The following functions are organized with the following sections:
- Working with a System
- Generating a Model
- Working with Nodes
- Working with Assets
- Working with Edges
- Working with Transformations
- Working with Storages
- Time Management
- Results Collection and Writing
Working with a System
Let's start by loading a system from a case folder (you can find more information about the structure of this folder in the Running Macro section).
load_case
julia> using MacroEnergy
julia> case = MacroEnergy.load_case("doctest");
[ Info: Loading case from doctest/system_data.json
[ Info: Loading system data
[ Info: Done loading system data. It took 0.39 seconds
[ Info: Done loading case data. It took 0.39 seconds
[ Info: *** Generating case ***
[ Info: Configuring case
[ Info: -- Setting solution algorithm
[ Info: -- Solution algorithm set to MacroEnergy.Monolithic()
[ Info: Generating systems
[ Info: Generating system
[ Info: ++ Creating new location: NE
[ Info: Done generating system. It took 9.2 seconds
[ Info: -- Discounting fixed costs for period 1
[ Info: -- Computing retirement case for period 1
[ Info: *** Done generating case. It took 10.18 seconds ***propertynames
The propertynames function in Julia can be used to retrieve the names of the fields of a System object, such as the data directory path, settings, and locations.
julia> propertynames(system)
(:data_dirpath, :settings, :commodities, :time_data, :assets, :locations, :input_data)data_dirpath: Path to the data directory.settings: Settings of the system.commodities: Sectors modeled in the system.timedata: Time resolution for each sector.locations: Vector of allLocations andNodes.assets: Vector of allAssets.
A System consists of six primary fields, each of which can be accessed using dot notation:
julia> system.data_dirpath
"doctest"
julia> system.settings
(ConstraintScaling = true, WriteSubcommodities = true, OverwriteResults = false, OutputDir = "results", OutputLayout = "long", AutoCreateNodes = false, AutoCreateLocations = true, Retrofitting = false, DualExportsEnabled = true)When interacting with a System, users might need to retrieve information about specific nodes, locations, or assets. The functions listed below are helpful for these tasks:
find_node
Finds a node by its ID.
julia> co2_node = MacroEnergy.find_node(system.locations, :co2_sink)get_asset_types
Retrieves all the types of assets in the system.
julia> asset_types = MacroEnergy.get_asset_types(system);
julia> unique(asset_types)
7-element Vector{DataType}:
Battery
Electrolyzer
GasStorage{Hydrogen}
ThermalPower{NaturalGas}
ThermalPower{Uranium}
TransmissionLink{Electricity}
VREasset_ids
Retrieves the IDs of all the assets in the system.
julia> ids = MacroEnergy.asset_ids(system)
102-element Vector{Symbol}:
:battery_SE
:battery_MIDAT
:battery_NE
:pumpedhydro_SE
:pumpedhydro_MIDAT
:pumpedhydro_NE
:SE_Electrolyzer
:MIDAT_Electrolyzer
:NE_Electrolyzer
:h2_SE_to_MIDAT
⋮
:existing_solar_MIDAT
:existing_solar_SE
:existing_solar_NE
:existing_wind_NE
:existing_wind_MIDATOnce you have the IDs, you can retrieve an asset by its ID using the following function:
get_asset_by_id
Retrieves an asset by its ID.
julia> battery_SE = MacroEnergy.get_asset_by_id(system, :battery_SE);
julia> thermal_plant_SE = MacroEnergy.get_asset_by_id(system, :SE_natural_gas_fired_combined_cycle_1);The following function can be useful to retrieve a vector of all the assets of a given type.
get_assets_sametype
Returns a vector of assets of a given type.
julia> batteries = MacroEnergy.get_assets_sametype(system, Battery);
julia> battery = batteries[1]; # first battery in the list
julia> thermal_plants = MacroEnergy.get_assets_sametype(system, ThermalPower{NaturalGas});
julia> thermal_plant = thermal_plants[1]; # first thermal power plant in the listModel Generation and Running
create_optimizer
Create an optimizer given a solver, optionally passing also environment and attributes:
optimizer = MacroEnergy.create_optimizer(HiGHS.Optimizer)generate_model
Uses JuMP to generate the optimization model for the system data.
julia> model = MacroEnergy.generate_model(case,optimizer);
[ Info: Generating model
[ Info: -- Period 1
[ Info: -- Adding linking variables
[ Info: -- Defining available capacity
[ Info: -- Generating planning model
[ Info: -- Including age-based retirements
[ Info: -- Generating operational model
[ Info: -- Model generation complete, it took 8.293462991714478 secondsoptimize!
Solves the optimization model.
julia> MacroEnergy.optimize!(model)The following set of functions can be used to retrieve the optimal values of some variables in the model.
get_optimal_capacity
Fetches the final capacities for all assets.
julia> capacity = MacroEnergy.get_optimal_capacity(system);
julia> capacity[!, [:commodity, :resource_id, :value]]
102×3 DataFrame
Row │ commodity resource_id value
│ Symbol Symbol Float64
─────┼──────────────────────────────────────────────────────────
1 │ Electricity battery_SE 7589.08
2 │ Electricity battery_MIDAT 1232.14
3 │ Electricity battery_NE -0.0
4 │ Electricity pumpedhydro_SE 6261.98
5 │ Electricity pumpedhydro_MIDAT 5244.0
6 │ Electricity pumpedhydro_NE 3206.9
7 │ Hydrogen SE_Electrolyzer 24638.1
8 │ Hydrogen MIDAT_Electrolyzer 38550.8
⋮ │ ⋮ ⋮ ⋮
98 │ Electricity existing_solar_MIDAT 2974.6
99 │ Electricity existing_solar_SE 8502.2
100 │ Electricity existing_solar_NE 0.0
101 │ Electricity existing_wind_NE 3654.5
102 │ Electricity existing_wind_MIDAT 3231.6
87 rows omittedget_optimal_new_capacity
Fetches the new capacities for all assets.
julia> new_capacity = MacroEnergy.get_optimal_new_capacity(system);
julia> new_capacity[!, [:commodity, :resource_id, :value]]
102×3 DataFrame
Row │ commodity resource_id value
│ Symbol Symbol Float64
─────┼──────────────────────────────────────────────────────────
1 │ Electricity battery_SE 7589.08
2 │ Electricity battery_MIDAT 1232.14
3 │ Electricity battery_NE 0.0
4 │ Electricity pumpedhydro_SE 0.0
5 │ Electricity pumpedhydro_MIDAT 0.0
6 │ Electricity pumpedhydro_NE 0.0
7 │ Hydrogen SE_Electrolyzer 24638.1
8 │ Hydrogen MIDAT_Electrolyzer 38550.8
⋮ │ ⋮ ⋮ ⋮
98 │ Electricity existing_solar_MIDAT 0.0
99 │ Electricity existing_solar_SE 0.0
100 │ Electricity existing_solar_NE 0.0
101 │ Electricity existing_wind_NE 0.0
102 │ Electricity existing_wind_MIDAT 0.0
87 rows omittedget_optimal_retired_capacity
Fetches the retired capacities for all assets.
julia> retired_capacity = MacroEnergy.get_optimal_retired_capacity(system);
julia> retired_capacity[!, [:commodity, :resource_id, :value]]
102×3 DataFrame
Row │ commodity resource_id value
│ Symbol Symbol Float64
─────┼─────────────────────────────────────────────────────────
1 │ Electricity battery_SE 0.0
2 │ Electricity battery_MIDAT 0.0
3 │ Electricity battery_NE 0.0
4 │ Electricity pumpedhydro_SE 0.0
5 │ Electricity pumpedhydro_MIDAT 0.0
6 │ Electricity pumpedhydro_NE 0.0
7 │ Hydrogen SE_Electrolyzer 0.0
8 │ Hydrogen MIDAT_Electrolyzer 0.0
⋮ │ ⋮ ⋮ ⋮
98 │ Electricity existing_solar_MIDAT 0.0
99 │ Electricity existing_solar_SE 0.0
100 │ Electricity existing_solar_NE 1629.6
101 │ Electricity existing_wind_NE 0.0
102 │ Electricity existing_wind_MIDAT 0.0
87 rows omittedSee the Results Collection and Writing section for more information on how to write the results to a file.
Working with Nodes in a System
Once a System object is loaded, and the model is generated, users can use the following functions to inspect the nodes in the system.
For a comprehensive list of function interfaces available for node besides id, commodity_type and the ones listed below, users can refer to the node.jl and the vertex.jl source code.
find_node
Finds a node in the System by its ID.
julia> elec_node = MacroEnergy.find_node(system.locations, :elec_SE);Nodes, as explained in the Macro Internal Components section, are a unique type of vertex that represent the demand or supply of a commodity, where each vertex in Macro is associated with a balance equation. To programmatically access all balance equations within the system, the following functions are available:
balance_ids: Retrieve the IDs of all balance equations associated with a vertex.get_balance: Obtain the mathematical expression of a specific balance equation.balance_data: Access the input balance data, which typically includes the stoichiometric coefficients of a specific balance equation, if applicable.
Here is an example of how to use these functions to access the balance equations for the electricity node in the system:
balance_ids
Retrieves the IDs of all balance equations in a node.
julia> MacroEnergy.balance_ids(elec_node)
1-element Vector{Symbol}:
:demandMacro automatically creates a :demand balance equation for each node that has a BalanceConstraint.
get_balance
Retrieves the mathematical expression of the demand balance equation for the node.
julia> demand_expression = MacroEnergy.get_balance(elec_node, :demand);
julia> demand_expression[1] # first time step
-102999 vREF + vNSD_elec_SE_period1[1,1] + vFLOW_battery_SE_discharge_edge_period1[1] - vFLOW_battery_SE_charge_edge_period1[1] + vFLOW_pumpedhydro_SE_discharge_edge_period1[1] - vFLOW_pumpedhydro_SE_charge_edge_period1[1] - vFLOW_SE_Electrolyzer_elec_edge_period1[1] - vFLOW_h2_SE_to_MIDAT_charge_elec_edge_period1[1] - vFLOW_h2_SE_to_MIDAT_discharge_elec_edge_period1[1] + vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] + vFLOW_SE_naturalgas_ctavgcf_moderate_0_elec_edge_period1[1] + vFLOW_SE_nuclear_2_elec_edge_period1[1] + vFLOW_SE_nuclear_mid_0_elec_edge_period1[1] - vFLOWPOS_SE_to_MIDAT_transmission_edge_period1[1] + vFLOW_SE_landbasedwind_class4_moderate_70_7_edge_period1[1] + vFLOW_SE_landbasedwind_class4_moderate_70_8_edge_period1[1] + vFLOW_existing_solar_SE_edge_period1[1]balance_ids
julia> co2_node = MacroEnergy.find_node(system.locations, :co2_sink);
julia> MacroEnergy.balance_ids(co2_node)
1-element Vector{Symbol}:
:emissionsMacro automatically creates an :emissions balance equation for each CO₂ node that has a CO2CapConstraint.
get_balance
julia> emissions_expression = MacroEnergy.get_balance(co2_node, :emissions);
julia> emissions_expression[1] # first time step
vFLOW_MIDAT_natural_gas_fired_combined_cycle_1_co2_edge_period1[1] + vFLOW_MIDAT_natural_gas_fired_combined_cycle_2_co2_edge_period1[1] + vFLOW_NE_naturalgas_ctavgcf_moderate_0_co2_edge_period1[1] + vFLOW_SE_nuclear_1_co2_edge_period1[1] + vFLOW_SE_nuclear_2_co2_edge_period1[1] + vFLOW_NE_nuclear_1_co2_edge_period1[1] + vFLOW_NE_nuclear_2_co2_edge_period1[1] + vFLOW_MIDAT_nuclear_1_co2_edge_period1[1] + vFLOW_MIDAT_nuclear_2_co2_edge_period1[1] + vFLOW_MIDAT_nuclear_mid_0_co2_edge_period1[1] + vFLOW_NE_nuclear_mid_0_co2_edge_period1[1] + vFLOW_SE_nuclear_mid_0_co2_edge_period1[1]To calculate the total emissions at a node, users should perform the following steps:
julia> emissions_expression = MacroEnergy.get_balance(co2_node, :emissions);
julia> MacroEnergy.value(sum(emissions_expression))
0.0To check and visualize the mathematical expressions of the constraints applied to a node, the following functions are available:
all_constraints: Retrieve all constraints associated with a node.all_constraints_types: Retrieve all types of constraints associated with a node.get_constraint_by_type: Retrieve a specific constraint on a node by its type.
all_constraints
Retrieves all the constraints attached to a node.
julia> all_constraints = MacroEnergy.all_constraints(elec_node);all_constraints_types
Retrieves all the types of constraints attached to a node.
julia> all_constraints_types = MacroEnergy.all_constraints_types(elec_node)
3-element Vector{DataType}:
MaxNonServedDemandPerSegmentConstraint
MaxNonServedDemandConstraint
BalanceConstraintget_constraint_by_type, constraint_ref
Retrieves a constraint on a node by its type.
julia> balance_constraint = MacroEnergy.get_constraint_by_type(elec_node, BalanceConstraint);
julia> MacroEnergy.constraint_ref(balance_constraint);
julia> max_non_served_demand_constraint = MacroEnergy.get_constraint_by_type(elec_node, MaxNonServedDemandConstraint);
julia> MacroEnergy.constraint_ref(max_non_served_demand_constraint)[1:5]
1-dimensional DenseAxisArray{JuMP.ConstraintRef,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.ConstraintRef}:
vNSD_elec_SE_period1[1,1] ≤ 102999
vNSD_elec_SE_period1[1,2] ≤ 98121
vNSD_elec_SE_period1[1,3] ≤ 95100
vNSD_elec_SE_period1[1,4] ≤ 93727
vNSD_elec_SE_period1[1,5] ≤ 93366Working with Assets
Together with Locations, assets form the core components of an energy system in Macro. The functions below are essential for managing and interacting with assets.
id
Retrieves the ID of an asset.
julia> thermal_plant = MacroEnergy.get_asset_by_id(system, :SE_natural_gas_fired_combined_cycle_1);
julia> MacroEnergy.id(thermal_plant)
:SE_natural_gas_fired_combined_cycle_1print_struct_info
Prints the structure of an asset in terms of its components (edges, transformations, storages, etc.)
julia> MacroEnergy.print_struct_info(thermal_plant)
Field: thermal_transform, Type: Transformation
Field: elec_edge, Type: Union{Edge{<:Electricity}, EdgeWithUC{<:Electricity}}
Field: fuel_edge, Type: Edge{<:NaturalGas}
Field: co2_edge, Type: Edge{<:CO2}Once you have collected the names of the components of an asset, you can use the following function to get a specific component by its name.
get_component_by_fieldname
Retrieves a component of an asset by its field name.
julia> elec_edge = MacroEnergy.get_component_by_fieldname(thermal_plant, :elec_edge);
julia> MacroEnergy.id(elec_edge)
:SE_natural_gas_fired_combined_cycle_1_elec_edge
julia> MacroEnergy.typeof(elec_edge)
EdgeWithUC{Electricity}
julia> MacroEnergy.commodity_type(elec_edge)
ElectricityAlternatively, users can retrieve a specific component using its ID.
get_component_ids
Retrieves the IDs of all the components of an asset.
julia> MacroEnergy.get_component_ids(thermal_plant)
4-element Vector{Symbol}:
:SE_natural_gas_fired_combined_cycle_1_transforms
:SE_natural_gas_fired_combined_cycle_1_elec_edge
:SE_natural_gas_fired_combined_cycle_1_fuel_edge
:SE_natural_gas_fired_combined_cycle_1_co2_edgeget_component_by_id
Retrieves a component of an asset by its ID.
julia> elec_edge = MacroEnergy.get_component_by_id(thermal_plant, :SE_natural_gas_fired_combined_cycle_1_elec_edge);
julia> MacroEnergy.id(elec_edge)
:SE_natural_gas_fired_combined_cycle_1_elec_edge
julia> MacroEnergy.typeof(elec_edge)
EdgeWithUC{Electricity}Working with Edges
id
Retrieves the ID of an edge.
julia> MacroEnergy.id(elec_edge)
:SE_natural_gas_fired_combined_cycle_1_elec_edgecommodity_type
Retrieves the commodity type of an edge.
julia> MacroEnergy.commodity_type(elec_edge)
ElectricityFor a comprehensive list of function interfaces available for edge besides id, commodity_type and the ones listed below, users can refer to the edge.jl source code.
get_edges
Retrieves all the edges in the system.
julia> edges = MacroEnergy.get_edges(system);capacity
Retrieves the capacity expression of an edge.
capacity_expression = MacroEnergy.capacity(elec_edge)
vCAP_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1final_capacity
Retrieves the final capacity of an edge (i.e. the optimal value of the capacity expression).
julia> capacity_expression = MacroEnergy.capacity(elec_edge)
vCAP_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1
julia> MacroEnergy.value(capacity_expression)
0.0flow
Retrieves the flow variables of an edge.
julia> flow_variables = MacroEnergy.flow(elec_edge);
julia> flow_variables[1:5]
1-dimensional DenseAxisArray{JuMP.VariableRef,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.VariableRef}:
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1]
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[2]
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[3]
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[4]
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[5]value
Retrieves the values of the flow variables of an edge.
julia> flow_values = MacroEnergy.value.(flow_variables);
julia> flow_values[1:5]
1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{Float64}:
-0.0
-0.0
-0.0
-0.0
-0.0Note that the value function is called with the dot notation to apply it to each element of the flow_variables array (see Julia's documentation for more information).
In this example, we first get the flow of the CO₂ edge and then we call the value function to get the values of these variables.
julia> co2_edge = MacroEnergy.get_component_by_fieldname(thermal_plant, :co2_edge);
julia> emission = MacroEnergy.flow(co2_edge)[1:5]
1-dimensional DenseAxisArray{JuMP.VariableRef,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.VariableRef}:
vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[1]
vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[2]
vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[3]
vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[4]
vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[5]
julia> emission_values = MacroEnergy.value.(emission);
julia> emission_values[1:5]
1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0The functions available for nodes when dealing with constraints can also be used for edges.
all_constraints_types
julia> MacroEnergy.all_constraints_types(elec_edge)
5-element Vector{DataType}:
MinFlowConstraint
MinDownTimeConstraint
CapacityConstraint
MinUpTimeConstraint
RampingLimitConstraintget_constraint_by_type
julia> constraint = MacroEnergy.get_constraint_by_type(elec_edge, CapacityConstraint);
julia> MacroEnergy.constraint_ref(constraint)[1:5]
1-dimensional DenseAxisArray{JuMP.ConstraintRef,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.ConstraintRef}:
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] - 504.206 vCOMMIT_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] ≤ 0
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[2] - 504.206 vCOMMIT_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[2] ≤ 0
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[3] - 504.206 vCOMMIT_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[3] ≤ 0
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[4] - 504.206 vCOMMIT_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[4] ≤ 0
vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[5] - 504.206 vCOMMIT_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[5] ≤ 0start_vertex
Retrieves the starting node of an edge.
julia> start_node = MacroEnergy.start_vertex(elec_edge);
julia> MacroEnergy.id(start_node)
:SE_natural_gas_fired_combined_cycle_1_transforms
julia> MacroEnergy.typeof(start_node)
Transformationend_vertex
Retrieves the ending node of an edge.
julia> end_node = MacroEnergy.end_vertex(elec_edge);
julia> MacroEnergy.id(end_node)
:elec_SE
julia> MacroEnergy.typeof(end_node)
Node{Electricity}Working with Transformations
For a comprehensive list of function interfaces available for transformation besides id, commodity_type and the ones listed below, users can refer to the transformation.jl and the vertex.jl source code.
To access the transformation component of an asset, utilize the following functions:
julia> MacroEnergy.print_struct_info(thermal_plant)
Field: thermal_transform, Type: Transformation
Field: elec_edge, Type: Union{Edge{<:Electricity}, EdgeWithUC{<:Electricity}}
Field: fuel_edge, Type: Edge{<:NaturalGas}
Field: co2_edge, Type: Edge{<:CO2}
julia> thermal_transform = MacroEnergy.get_component_by_fieldname(thermal_plant, :thermal_transform);
julia> MacroEnergy.id(thermal_transform)
:SE_natural_gas_fired_combined_cycle_1_transforms
julia> MacroEnergy.typeof(thermal_transform)
Transformationbalance_ids
Retrieves the IDs of all the balance equations in a transformation.
julia> MacroEnergy.balance_ids(thermal_transform)
2-element Vector{Symbol}:
:emissions
:energybalance_data
Retrieves the balance data of a transformation. This is very useful to check the stoichiometric coefficients of a transformation.
julia> MacroEnergy.balance_data(thermal_transform, :energy)
Dict{Symbol, Float64} with 3 entries:
:SE_natural_gas_fired_combined_cycle_1_fuel_edge => 1.0
:SE_natural_gas_fired_combined_cycle_1_elec_edge => 2.13209
:SE_natural_gas_fired_combined_cycle_1_co2_edge => 0.0get_balance
Retrieves the mathematical expression of the balance of a transformation.
julia> MacroEnergy.get_balance(thermal_transform, :energy)[1:5]
1-dimensional DenseAxisArray{JuMP.AffExpr,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.AffExpr}:
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[1]
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[2] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[2] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[2]
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[3] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[3] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[3]
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[4] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[4] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[4]
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[5] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[5] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[5]We can do the same for the emissions balance equation.
balance_data
julia> MacroEnergy.balance_data(thermal_transform, :emissions)
Dict{Symbol, Float64} with 3 entries:
:SE_natural_gas_fired_combined_cycle_1_fuel_edge => 0.181048
:SE_natural_gas_fired_combined_cycle_1_elec_edge => 0.0
:SE_natural_gas_fired_combined_cycle_1_co2_edge => 1.0get_balance
julia> MacroEnergy.get_balance(thermal_transform, :emissions)[1:5]
1-dimensional DenseAxisArray{JuMP.AffExpr,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.AffExpr}:
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[1] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[1]
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[2] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[2]
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[3] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[3]
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[4] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[4]
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[5] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[5]The functions available for nodes and edges can also be applied to transformations.
all_constraints
julia> MacroEnergy.all_constraints(thermal_transform)
1-element Vector{AbstractTypeConstraint}:
BalanceConstraint(missing, missing, 2-dimensional DenseAxisArray{JuMP.ConstraintRef,2,...} with index sets:
Dimension 1, [:emissions, :energy]
Dimension 2, 1:1:24
And data, a 2×24 Matrix{JuMP.ConstraintRef}:
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[1] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[1] = 0 … 0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[24] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[24] = 0
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[1] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[1] = 0 -2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[24] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[24] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[24] = 0)all_constraints_types
julia> MacroEnergy.all_constraints_types(thermal_transform)
1-element Vector{DataType}:
BalanceConstraintget_constraint_by_type
julia> MacroEnergy.get_constraint_by_type(thermal_transform, BalanceConstraint)
BalanceConstraint(missing, missing, 2-dimensional DenseAxisArray{JuMP.ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, JuMP.ScalarShape},2,...} with index sets:
Dimension 1, [:emissions, :energy]
Dimension 2, 1:1:24
And data, a 2×24 Matrix{JuMP.ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, JuMP.ScalarShape}}:
0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[1] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[1] = … 0.181048235160161 vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[24] - vFLOW_SE_natural_gas_fired_combined_cycle_1_co2_edge_period1[24] = 0
-2.132092034 vFLOW_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[24] - 295.53638384084 vSTART_SE_natural_gas_fired_combined_cycle_1_elec_edge_period1[24] + vFLOW_SE_natural_gas_fired_combined_cycle_1_fuel_edge_period1[24] = 0)Working with Storages
For a comprehensive list of function interfaces available for storage besides id, commodity_type and the ones listed below, users can refer to the storage.jl and the vertex.jl source code.
To access the storage component of an asset, utilize the following functions:
julia> battery = MacroEnergy.get_asset_by_id(system, :battery_SE);
julia> MacroEnergy.print_struct_info(battery)
Field: battery_storage, Type: AbstractStorage{<:Electricity}
Field: discharge_edge, Type: Edge{<:Electricity}
Field: charge_edge, Type: Edge{<:Electricity}
julia> storage = MacroEnergy.get_component_by_fieldname(battery, :battery_storage);
julia> MacroEnergy.id(storage)
:battery_SE_storage
julia> MacroEnergy.typeof(storage)
Storage{Electricity}balance_ids
Retrieves the IDs of all the balance equations in a storage.
julia> MacroEnergy.balance_ids(storage)
1-element Vector{Symbol}:
:storagebalance_data
Retrieves the balance data of a storage. This is very useful to check the stoichiometric coefficients of a storage.
julia> MacroEnergy.balance_data(storage, :storage)
Dict{Symbol, Float64} with 2 entries:
:battery_SE_discharge_edge => 1.08696
:battery_SE_charge_edge => 0.92get_balance
Retrieves the mathematical expression of the balance of a storage.
julia> MacroEnergy.get_balance(storage, :storage)[1:5]
1-dimensional DenseAxisArray{JuMP.AffExpr,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.AffExpr}:
-vSTOR_battery_SE_storage_period1[1] + vSTOR_battery_SE_storage_period1[12] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[1] + 0.92 vFLOW_battery_SE_charge_edge_period1[1]
-vSTOR_battery_SE_storage_period1[2] + vSTOR_battery_SE_storage_period1[1] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[2] + 0.92 vFLOW_battery_SE_charge_edge_period1[2]
-vSTOR_battery_SE_storage_period1[3] + vSTOR_battery_SE_storage_period1[2] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[3] + 0.92 vFLOW_battery_SE_charge_edge_period1[3]
-vSTOR_battery_SE_storage_period1[4] + vSTOR_battery_SE_storage_period1[3] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[4] + 0.92 vFLOW_battery_SE_charge_edge_period1[4]
-vSTOR_battery_SE_storage_period1[5] + vSTOR_battery_SE_storage_period1[4] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[5] + 0.92 vFLOW_battery_SE_charge_edge_period1[5]The same set of functions that we have seen for nodes, edges, and transformations are also available for storages.
all_constraints_types
julia> MacroEnergy.all_constraints_types(storage)
5-element Vector{DataType}:
StorageMinDurationConstraint
StorageCapacityConstraint
StorageMaxDurationConstraint
StorageSymmetricCapacityConstraint
BalanceConstraintget_constraint_by_type
julia> MacroEnergy.get_constraint_by_type(storage, BalanceConstraint)
BalanceConstraint(missing, missing, 2-dimensional DenseAxisArray{JuMP.ConstraintRef,2,...} with index sets:
Dimension 1, [:storage]
Dimension 2, 1:1:24
And data, a 1×24 Matrix{JuMP.ConstraintRef}:
-vSTOR_battery_SE_storage_period1[1] + vSTOR_battery_SE_storage_period1[12] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[1] + 0.92 vFLOW_battery_SE_charge_edge_period1[1] = 0 … vSTOR_battery_SE_storage_period1[23] - vSTOR_battery_SE_storage_period1[24] - 1.0869565217391304 vFLOW_battery_SE_discharge_edge_period1[24] + 0.92 vFLOW_battery_SE_charge_edge_period1[24] = 0)
julia> constraint = MacroEnergy.get_constraint_by_type(storage, StorageCapacityConstraint);
julia> MacroEnergy.constraint_ref(constraint)[1:5]
1-dimensional DenseAxisArray{JuMP.ConstraintRef,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{JuMP.ConstraintRef}:
-vCAP_battery_SE_storage_period1 + vSTOR_battery_SE_storage_period1[1] ≤ 0
-vCAP_battery_SE_storage_period1 + vSTOR_battery_SE_storage_period1[2] ≤ 0
-vCAP_battery_SE_storage_period1 + vSTOR_battery_SE_storage_period1[3] ≤ 0
-vCAP_battery_SE_storage_period1 + vSTOR_battery_SE_storage_period1[4] ≤ 0
-vCAP_battery_SE_storage_period1 + vSTOR_battery_SE_storage_period1[5] ≤ 0storage_level
Retrieves the storage level variables of a storage component.
julia> storage_level = MacroEnergy.storage_level(storage);
julia> MacroEnergy.value.(storage_level)[1:5]
1-dimensional DenseAxisArray{Float64,1,...} with index sets:
Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{Float64}:
-0.0
-0.0
-0.0
-0.0
-0.0charge_edge
Retrieves the charge edge connected to a storage component.
julia> charge_edge = MacroEnergy.charge_edge(storage);
julia> MacroEnergy.id(charge_edge)
:battery_SE_charge_edge
julia> MacroEnergy.typeof(charge_edge)
Edge{Electricity}discharge_edge
Retrieves the discharge edge connected to a storage component.
julia> discharge_edge = MacroEnergy.discharge_edge(storage);
julia> MacroEnergy.id(discharge_edge)
:battery_SE_discharge_edge
julia> MacroEnergy.typeof(discharge_edge)
Edge{Electricity}spillage_edge
Retrieves the spillage edge connected to a storage component (applicable to hydro reservoirs).
julia> MacroEnergy.spillage_edge(storage)Time Management
julia> vertex = MacroEnergy.find_node(system.locations, :elec_SE);
julia> edge = MacroEnergy.get_component_by_fieldname(thermal_plant, :elec_edge);time_interval
Retrieves the time interval of a vertex/edge.
julia> MacroEnergy.time_interval(vertex)
1:1:24
julia> MacroEnergy.time_interval(edge)
1:1:24period_map
Retrieves the period map of a vertex/edge.
julia> MacroEnergy.subperiod_map(vertex)
Dict{Int64, Int64} with 2 entries:
2 => 2
1 => 1modeled_subperiods
Retrieves the modeled subperiods of a vertex/edge.
julia> MacroEnergy.modeled_subperiods(vertex)
2-element Vector{Int64}:
1
2current_subperiod
Retrieves the subperiod a given time step belongs to for the time series attached to a given vertex/edge.
julia> MacroEnergy.current_subperiod(vertex, 7)
1subperiods
Retrieves the subperiods of the time series attached to a vertex/edge.
julia> MacroEnergy.subperiods(vertex)
2-element Vector{StepRange{Int64, Int64}}:
1:1:12
13:1:24subperiod_indices
Retrieves the indices of the subperiods of the time series attached to a vertex/edge.
julia> MacroEnergy.subperiod_indices(vertex)
2-element Vector{Int64}:
1
2get_subperiod
Retrieves the subperiod of a vertex/edge for a given index.
julia> MacroEnergy.get_subperiod(vertex, 2)
13:1:24subperiod_weight
Retrieves the weight of a subperiod of a vertex/edge for a given index.
julia> MacroEnergy.subperiod_weight(vertex, 2)
365.0Results Collection and Writing
reshape_wide
Reshapes the results to wide format.
julia> capacity_results = MacroEnergy.get_optimal_capacity(system; scaling=1e3);
julia> new_capacity_results = MacroEnergy.get_optimal_new_capacity(system; scaling=1e3);
julia> retired_capacity_results = MacroEnergy.get_optimal_retired_capacity(system; scaling=1e3);
julia> all_capacity_results = vcat(capacity_results, new_capacity_results, retired_capacity_results);
julia> df_wide = MacroEnergy.reshape_wide(all_capacity_results);
julia> df_wide[1:5, [:commodity, :resource_id, :capacity, :new_capacity, :retired_capacity]]
5×5 DataFrame
Row │ commodity resource_id capacity new_capacity retired_capac ⋯
│ Symbol Symbol Float64? Float64? Float64? ⋯
─────┼──────────────────────────────────────────────────────────────────────────
1 │ Electricity battery_SE 7.58908e6 7.58908e6 ⋯
2 │ Electricity battery_MIDAT 1.23214e6 1.23214e6
3 │ Electricity battery_NE -0.0 0.0
4 │ Electricity pumpedhydro_SE 6.26198e6 0.0
5 │ Electricity pumpedhydro_MIDAT 5.244e6 0.0 ⋯
1 column omittedwrite_flow
Writes the flow results to a (CSV, CSV.GZ, or Parquet) file. An optional commodity and asset type filter can be applied.
julia> write_flow("flow.csv", system)
# Filter by commodity: write only the flow of edges of commodity "Electricity"
julia> write_flow("flow.csv", system, commodity="Electricity")
# Filter by commodity and asset type using parameter-free matching
julia> write_flow("flow.csv", system, commodity="Electricity", asset_type="ThermalPower")
# Filter by commodity and asset type using wildcard matching
julia> write_flow("flow.csv", system, commodity="Electricity", asset_type="ThermalPower*")write_capacity
Writes the capacity results to a (CSV, CSV.GZ, or Parquet) file. An optional commodity and asset type filter can be applied.
julia> write_capacity("capacity.csv", system)
# Filter by commodity: write only the capacity of edges of commodity "Electricity"
julia> write_capacity("capacity.csv", system, commodity="Electricity")
# Filter by commodity and asset type using parameter-free matching
julia> write_capacity("capacity.csv", system, asset_type="ThermalPower")
# Filter by asset type using wildcard matching
julia> write_capacity("capacity.csv", system, asset_type="ThermalPower*")
# Filter by commodity and asset type
julia> write_capacity("capacity.csv", system, commodity="Electricity", asset_type=["ThermalPower", "Battery"])write_costs
Writes the costs results to a (CSV, CSV.GZ, or Parquet) file. An optional type filter can be applied.
julia> write_costs("costs.csv", system, model)write_settings
julia> write_settings(case, "settings.json")This function exports case and system settings to a JSON file, useful for debugging and documentation.
write_results
The write_results function is part of the legacy unified output system. For new code, consider using the specialized output functions instead.
julia> write_results(file_path, system, model, settings, ext=".csv.gz")
julia> write_results(file_path, system, model, settings, ext=".parquet")This function creates multiple output files, one for each result type:
file_path_capacity.ext- Capacity resultsfile_path_flow.ext- Flow resultsfile_path_non_served_demand.ext- Non-served demandfile_path_storage_level.ext- Storage levelsfile_path_discounted_costs.ext- Discounted costsfile_path_undiscounted_costs.ext- Undiscounted costs